This chapter complements the "Calling Procedures in DLLs" chapter in the Visual Basic 4.0 Programmer's Guide by showing you how to build a simple DLL and demonstrating some of the tools that Microsoft has provided to enable you to investigate
the DLL's characteristics.
This chapter does not discuss the inline OLE servers you can create within Visual Basic 4.0 because they are implemented as logically partitioned sections of the same interpreted threaded p-code, which is used for all other Visual Basic 4.0 executables.
Although these inline OLE servers perform a function similar to a true OLE server (which you might be able to make if you are an expert with the latest version of the Microsoft foundation classes and the C++ language), they are not optimized machine code
DLLs in any way that is commonly understood. You should consult the Visual Basic 4.0 manuals for example code and other information on inline OLE servers.
Dynamic link libraries (DLLs) are executable modules containing functions that Windows applications can call to perform useful service tasks. DLLs play a central role in Windows applications; they make its resources and functions available to Windows
applications. Actually, all Windows libraries are dynamic link libraries. These libraries are called dynamic because they are linked with the application that needs them at runtime, instead of being statically linked as a final act of the software's build
process.
DLLs are not loaded into memory until a function in the DLL is called for the first time.
The different linking methods have different benefits. Static linking can give you a stand-alone program that you know is complete, but dynamic linking enables a modular approach in which different components can be replaced individually. The DLL
approach enables components to share common executable code instead of duplicating the runtime code for each program.
From the point of view of a given DLL, any of its functions available for use by other applications are known as exported functions; if the DLL uses functions from other libraries, these are its imported functions. One DLL's exported functions are
another DLL's imported functions.
By the way, not all DLLs have DLL file name extensions—the Windows system files have .EXE extensions, Visual Basic controls are packaged DLLs with .VBX or .OCX extensions, drivers have .DRV extensions, and fonts have .FON extensions.
You need to be careful when running programs that use DLLs because the link is not performed until runtime, which means that you might pick up the wrong DLL if you have more than one copy of the DLL on your system. I have included some sample source
code (in Listing 20.3) that your main program can use to ask Windows components for their version number.
Visual Basic does not directly support pointers to variables, which causes difficulties when accessing a considerable portion of Windows API functions (which frequently pass pointers as parameters). This is not as big a problem as it might seem,
however, because Visual Basic can hold the value of pointers in integer variables (usually Long values). As long as you don't need to explicitly know what the contents of a pointer mean, you can receive a pointer value from one API and pass it back into
another API. As long as the first and last API calls in a sequence will take and return Visual Basic-compatible variables, respectively, you can proceed without any further complications.
Listing 20.1 shows a code example where hOldWnd% and lbhWnd% are used to validly hold the values of non-Visual Basic data because Visual Basic does not need access to the values. A Windows API call SendMessage() is used to make a Visual Basic listbox
behave in a way beyond what usually is available in Visual Basic by appearing to support synchronized multiple columns. Figure 20.1 shows the resulting listbox.
Figure 20.1. Using Windows APIs to extend the functionality of the Listbox control.
Declare Function GetFocus Lib "user" () As Integer Declare Function SendMessage Lib "user" (ByVal hWnd As Integer, ByVal wMsg As _Integer, ByVal wp As Integer, lp As Any) As Long Declare Function PutFocus Lib "user" Alias "SetFocus" (ByVal hWnd%) As Integer Const WM_USER = &H400 Const LB_SETTABSTOPS = WM_USER + 19 Sub Form_Click() Dim retVal&, R% 'API return values Dim hOldWnd%, lbhWnd% 'WinCtrl handles Static tabs(3) As Integer tabs(1) = 10 'Set up array of defined tab stops. tabs(2) = 70 tabs(3) = 130 hOldWnd% = GetFocus() 'Remember who had the focus. Form1.Show 'Showing the form avoids "Illegal ' Function Call" on 'List1.SetFocus' list1.SetFocus 'Set the focus to the list box. lbhWnd% = GetFocus() 'Get the handle to the list box. 'Send a message to the message queue. retVal& = SendMessage(lbhWnd%, LB_SETTABSTOPS, 3, tabs(1)) R% = PutFocus(hOldWnd%) 'Restore handle to whoever had it. 'Place some elements into the list box, separated by TAB chars: list1.AddItem "Last Name" + Chr$(9) + "First Name" + Chr$(9) + "Year" list1.AddItem "Washington" + Chr$(9) + "George" + Chr$(9) + "1789" list1.AddItem "Adams" + Chr$(9) + "John" + Chr$(9) + "1797" list1.AddItem "Jefferson" + Chr$(9) + "Thomas" + Chr$(9) + "1801" list1.AddItem "Madison" + Chr$(9) + "James" + Chr$(9) + "1809" list1.AddItem "Monroe" + Chr$(9) + "James" + Chr$(9) + "1817" list1.AddItem "Adams" + Chr$(9) + "John Q." + Chr$(9) + "1825" End Sub
A further limitation of Visual Basic is that you have to explicitly declare your DLL function calls at design time, which makes runtime binding of unspecified device driver libraries impossible within Visual Basic alone.
In order to provide the optimum design-time flexibility, Visual Basic 4.0 is implemented as an interpreter that processes semicompiled programs. The code is analyzed and internally represented so that the code can be reconstituted for display purposes,
yet have a minimum interpretation overhead at runtime. Therefore, due to its interpreted nature, few of the optimizations are available that might otherwise be attempted during a true reduction to native machine code (such as that offered by the compiler
shipped with Visual C++).
This limitation is not necessarily significant, because the target computer specification may be sufficiently generous to run the Visual Basic code within the user's limits of patience. However, if many iterative processes are required that take far too
long to execute in spite of the careful removal of inefficiencies, you can take advantage of the superior performance of optimized pure machine code by rewriting the time-consuming sections of the program in a language that has a suitable compiler
available.
Be aware, though, that the standard of programming competence and thoroughness required for writing Windows DLLs is very high. Failures of Visual Basic programs due to general protection faults (GPFs) almost always are due to rogue DLL
programming—usually within libraries not supplied by Microsoft. You should try to remove all errors and warnings from your compiler output with the warning level set to the highest value available (with a few possible exceptions, such as the //
comment symbol not being to the ANSI standard).
Although there is no intrinsic reason why a DLL could not be built using a BASIC optimizing compiler, there are no vendors of Microsoft's size and presence that sell one (at least not at the time of this writing). Therefore, if you want to build a DLL
to use with your Visual Basic program, you must write it in a different language. Although vendors such as Borland sell PASCAL compilers that can be used to create DLLs, Microsoft's languages of choice are C or C++, which can be compiled using Microsoft's
Visual C++ product.
Building a DLL is a much easier job these days than it used to be! The process I've put in Listings 20.11 and onwards (at the end of this chapter) used to be standard practice before visual tools such as Microsoft's Visual C++ or Borland's Pascal and
C++ products for Windows became available.
To create a simple DLL using Visual C++, follow these steps:
Figure 20.2. Visual C++—defining a new project.
Figure 20.3. Example source code within Visual C++.
Figure 20.4. Choosing files to add to the project.
Figure 20.5. Adding and editing the Module Definition file.
Figure 20.6. Building the DLL.
For 32-bit compiling, you replace the "int _far _pascal" with a single keyword "__declspec(dllexport) int" and use VC++ version 2.0 onwards instead of the 16-bit VC++ version 1.5 :
/************** * SCALC_32.C * *************/ __declspec(dllexport) int SImpleCalc(int Parm1,int Parm2); __declspec(dllexport) int SImpleCalc(int Parm1,int Parm2) { return (Parm1 * Parm2); }
The DLL you just created can be declared in the code, and then run from a Visual Basic 4.0 event routine, such as the one shown in Listing 20.2 and demonstrated in Figure 20.7.
Figure 20.7. Running a DLL with Visual Basic.
Declare Function SimpleCalc% Lib "C:\SC_WIN\SCALCWIN" (ByVal Parm1%, ByVal _Parm2%) Private Sub Label3_Click() Dim Parm1 As Integer Dim Parm2 As Integer Parm1 = Val(Text1.Text) Parm2 = Val(Text2.Text) Label3.Caption = Format$(SimpleCalc(Parm1, Parm2)) End Sub
If you are building more sophisticated DLLs, you should consider using the Resource Compiler (RC.EXE) for features such as version information. You also might want to use IMPLIB.EXE to handle imports if your DLL calls any functions from other DLLs. See
the Visual C++ documentation for more information on these utility programs.
Furthermore, if you're planning to make both 16-bit and 32-bit versions of your code, you should allow for certain subtleties in the function declaration, such as that Win32 is case-sensitive regarding function names but Win16 is not. Also note that
32-bit Windows API calls often have the number 32 appended to the end of the 16-bit function name.
The best way to deal with this problem is to localize Visual Basic 4.0's logical link to the DLL to one place only by using the Alias keyword within the Function declaration. The only line of your code that will need to be altered in order to migrate to
32-bit, therefore, is the Declare line.
One of the trickiest issues to arise from having both 16-bit and 32-bit versions of Visual Basic 4.0 is that of ANSI strings (VB4/16—one byte per character) and UNICODE strings (VB4/32—two bytes per character).
To be sure of always using the same number of bytes, you should use the new Byte data type instead of Strings. An exception to this necessity is when a DLL is called using the established Visual Basic declaration syntax, in which all versions of Visual
Basic 4.0 will use the ANSI convention to pass strings. To force the passing of UNICODE, you should copy the string to a Byte array and pass that instead, or you should declare the function by using a TypeLib.
Likewise, with user-defined types (UDTs), it is important to ensure that the UDT defined in Visual Basic is of the same size as the C structure defined in the DLL. You can do this by enforcing a strict one-to-one alignment of corresponding members in
both UDTs.
The 16-bit version of Visual Basic 4.0 packs the UDT members on byte boundaries, and the 32-bit version packs the UDT members on 4-byte (DWORD) boundaries. You therefore should compile your 16-bit DLL code using a single byte struct alignment compiler
setting and your 32-bit DLL code using the 4-byte struct member alignment compiler setting.
You can aid full compatibility between versions by ordering the UDT members in a certain way: the >= 4-byte built-in data types first (Variant, Double, Currency, Single, and Long), followed by the remaining even-length data types, with the rest of
the datatypes completing the structure. Then most of your data types (except the last-mentioned types) will be inherently impervious to this consideration.
Remember that, although integers always are two bytes long for Visual Basic and 16-bit DLLs, they are four bytes long for 32-bit DLLs. Therefore, always declare your two-byte integers as Short in your DLLs.
In Visual Basic 3.0, you can use the Visual Basic API routines to access and modify DLL data types. In Visual Basic 4.0, you should use the OLE APIs to access your DLL data types.
Table 20.1 lists some of the Visual Basic 3.0 API routines and their Visual Basic 4.0 OLE equi-valents.
Visual Basic 3.0 API Routine |
Visual Basic 4.0 OLE Equivalent |
VBArrayBounds |
SafeArrayGetLBound/SafeArrayGetUBound |
VBArrayElement |
SafeArrayGetElement |
VBArrayElemSize |
SafeArrayGetElemsize |
VBArrayFirstElem |
N/A |
VBArrayIndexCount |
SafeArrayGetDim |
VBCoerceVariant |
VariantChangeType |
VBCreateHlstr |
SysAllocString/SysAllocStringLen |
VBCreateTempHlstr |
SysAllocString/SysAllocStringLen |
VBDerefHlstr |
N/A |
VBDerefHlstrLen |
N/A |
VBDerefZeroTermHlstr |
N/A |
VBDestroyHlstr |
SysFreeString |
VBGetHlstrLen |
SysStringLen |
VBGetVariantType |
N/A |
VBGetVariantValue |
N/A |
VBResizeHlstr |
SysReAllocStringLen |
VBSetHlstr |
SysReAllocString |
VBSetVariantValue |
N/A |
In Visual Basic 4.0, it is just as possible to save version information within executables as it always has been for DLLs via the Resource Compiler. This means that you can check all your software components at start-up to ensure that the correct
combination of component-builds is available; if they are not available, the application can be terminated gracefully before anything unexpected occurs (such as a crash due to a critical component of yours having been overwritten by a third-party install
program's different version of that component).
Listing 20.3 gives you some sample code so that you can read the version number of a DLL.
Type VS_VERSION wLength As Integer wValueLength As Integer szKey As String * 16 ' "VS_VERSION_INFO" dwSignature As Long ' VS_FIXEDFILEINFO struct dwStrucVersion As Long dwFileVersionMS As Long dwFileVersionLS As Long dwProductVersionMS As Long dwProductVersionLS As Long dwFileFlagsMasks As Long dwFileFlags As Long dwFileOS As Long dwFileType As Long dwFileSubType As Long dwFileDateMS As Long dwFileDateLS As Long End Type Declare Function GetFileVersionInfo% Lib "Ver.dll" (ByVal Filename$, ByVal _dwHandle&, ByVal cbBuff&, ByVal lpvData$) Declare Function GetFileVersionInfoSize& Lib "Ver.dll" (ByVal Filename$, _dwHandle&) Declare Sub hmemcpy Lib "kernel" (hpvDest As Any, hpvSrc As Any, ByVal cbBytes&) Function HIWORD (X As Long) As Integer HIWORD = X \ &HFFFF& End Function Function LOWORD (X As Long) As Integer LOWORD = X And &HFFFF& End Function Function VerInfo$ (FullFileName$) Dim X As VS_VERSION Dim FileVer$ BufSize& = GetFileVersionInfoSize(FullFileName$, dwHandle&) If BufSize& = 0 Then MsgBox "No Version Info available!" Exit Function End If lpvData$ = Space$(BufSize&) r% = GetFileVersionInfo(FullFileName$, dwHandle&, BufSize&, lpvData$) hmemcpy X, ByVal lpvData$, Len(X) FileVer$ = LTrim$(Str$(HIWORD(X.dwFileVersionMS))) + "." FileVer$ = FileVer$ + LTrim$(Str$(LOWORD(X.dwFileVersionMS))) + "." FileVer$ = FileVer$ + LTrim$(Str$(HIWORD(X.dwFileVersionLS))) + "." FileVer$ = FileVer$ + LTrim$(Str$(LOWORD(X.dwFileVersionLS))) VerInfo$ = FileVer$ End Function
Listing 20.3 can be run with a function call like this:
Sub Form_Load () Text1.Text = VerInfo$("c:\windows\system\vbrun300.dll") End Sub
See the "MS Knowledge Base" article Q112731 for more extensive information. It's available via CompuServe (GO MSKB) or via the MS Developer Network CDs.
Some useful tools are provided within Windows and the Software Development Kit (SDK) bundled with Visual C++, which enable you to examine DLLs to find out useful things about their contents. These tools are described in the following sections.
One of the most useful tools is EXEHDR.EXE, which enables you to read and print the DLL's header information in an intelligible format. Applying EXEHDR to the DLL you produced using Visual C++ above, for example, gives the output shown in Listing 20.4.
C:\SC_WIN>exehdr scalcwin.dll Microsoft (R) EXE File Header Utility Version 3.20 Copyright Microsoft Corp 1985-1993. All rights reserved. Library: SCALCWIN Description: SCALCWIN.exe Data: SHARED Initialization: Global Initial CS:IP: seg 1 offset 00a8 Initial SS:SP: seg 0 offset 0000 DGROUP: seg 2 Heap allocation: 0400 bytes Application type: WINDOWAPI Other module flags: Contains gangload area; start: 0x140; size 0xa00 no. type address file mem flags 1 CODE 00000160 007bd 007bd PRELOAD, (movable), (discardable) 2 DATA 000009c0 00170 00170 SHARED, PRELOAD, (movable) Exports: ord seg offset name 2 1 0000 WEP exported, shared data 3 1 040e ___EXPORTEDSTUB exported, shared data 1 1 07b0 SIMPLECALC exported, shared data
Listing 20.4 demonstrates that SIMPLECALC is indeed an exported function from your DLL, along with two other Windows functions: WEP and _EXPORTEDSTUB, which were built-in automatically due to the inclusion of the /GD parameter when the source code was
compiled. You also can derive from this listing an alternative means of calling the function; the first column gives the ordinal number of the function so that you can call SIMPLECALC by number as function #1 within the SCALCWIN library.
There also is an optional /VERBOSE parameter that you can apply to get even more information, such as the Windows functions that SCALCWIN.DLL uses itself (imports) as part of its operation. If you apply this parameter, you can derive that SCALCWIN uses
the functions shown in Listing 20.5 from the Windows Kernel (which I have shown in both ordinal and name form).
KERNEL.1 (FATALEXIT) KERNEL.3 (GETVERSION) KERNEL.4 (LOCALINIT) KERNEL.15 (GLOBALALLOC) KERNEL.16 (GLOBALREALLOC) KERNEL.17 (GLOBALFREE) KERNEL.18 (GLOBALLOCK) KERNEL.19 (GLOBALUNLOCK) KERNEL.20 (GLOBALSIZE) KERNEL.23 (LOCKSEGMENT) KERNEL.48 (GETMODULEUSAGE) KERNEL.91 (INITTASK) KERNEL.102 (DOS3CALL) KERNEL.131 (GETDOSENVIRONMENT) KERNEL.137 (FATALAPPEXIT) KERNEL.178 (__WINFLAGS)
While on the subject of EXEHDR, there's one important thing to mention—try renaming the C source file shown in Figure 20.3 so that it has a CPP extension instead of a C extension. If you rebuild the SCALCWIN project once more using a CPP file name,
you get the error shown in Figure 20.8.
Figure 20.8. Link failure caused by C++ name mangling.
This error occurs because of C++ name mangling (or name decoration, as Microsoft often describes it) where the compiler appends characters to the function name for its own internal purposes—which can render the function name unrecognizable to the
calling program.
It's possible to get around this linking problem by using the _export keyword on the function prototype itself, as shown in Listing 20.6 (instead of declaring the function name in the DEF linker definitions file).
/**************** * SCALCWIN.CPP * ****************/ int _export _far _pascal SimpleCalc(int Parm1, int Parm2); int _export _far _pascal SimpleCalc(int Parm1, int Parm2) { return (Parm1 * Parm2); }
However, even if you get around the linking problem, the effect of C++ name mangling is to mess up the EXEHDR Exports output, as shown in Listing 20.7.
Exports: ord seg offset name 1 1 0026 WEP exported, shared data 2 1 045e ___EXPORTEDSTUB exported, shared data 3 1 0000 ?SIMPLECALC@@ZCHHH@Z exported, shared data You need to add extern "C" to the function declaration to correct it :- /**************** * SCALCWIN.CPP * ****************/ extern "C" int _export _far _pascal SimpleCalc(int Parm1, int Parm2); extern "C" int _export _far _pascal SimpleCalc(int Parm1, int Parm2) { return (Parm1 * Parm2); }
Another useful Windows tool found in the SDK is HeapWalker, which you can use to inspect the global heap (the system memory that Windows uses) and local heaps used by active applications or DLLs currently loaded into memory. It's useful for analyzing
any memory allocation and deallocation effects when an application creates or destroys objects, so you can use it to verify that your application cleans up after itself properly when terminated—that is, it unloads all the DLLs it has used in order to
free the computer's memory for other applications.
HeapWalker also is more than a simple Windows-based dump program; it can display graphical objects correctly rather than merely displaying screensful of bytes.
To see how HeapWalker works, exit Windows and reboot your machine so that you can be sure that no orphan memory allocations remain. Run Windows and HeapWalker alone before doing anything else, and then page down until you find the entries for SHELL in
the OWNER column. If the correct Sort option has been chosen, you should see something like the lines shown in Listing 20.8 (probably with different memory addresses) for Windows 3.1x.
ADDRESS HANDLE SIZE LOCK FLG HEAP OWNER TYPE 806BD820 176E 512 D SHELL Code 1 806BBDC0 1766 6752 D SHELL Code 2 806BB3C0 175E 2560 D SHELL Code 3 806B9FC0 1756 5120 D SHELL Code 4 806B9D80 174E 576 D SHELL Code 5 806B9C20 1746 352 D SHELL Code 6 00015900 1716 32 SHELL Data 0004BF80 170E 1120 Y SHELL DGroup 0004B0C0 177E 864 SHELL Module Database 0004D6A0 151E 128 SHELL Private 00058960 1706 224 D SHELL Resource String
Minimize HeapWalker. Then choose Help | About from the main Windows menu and just cancel the resulting About dialog box. To perform this, a DLL had to be loaded; so if you maximize HeapWalker and search for the SHELL entries again, you should see that
the following extra lines have appeared:
8071EDE0 172E 3552 D SHELL Code 9 00015680 150E 576 D SHELL Resource Dialog 00059960 1FEE 2080 D SHELL Resource RCdata
These lines show that calling a Windows function has caused memory to be allocated but not freed afterward. You might wonder what these extra memory allocations are for; to find out, just double-click on the line that interests you. If you choose the
Resource Dialog line, for example, you see a screen similar to Figure 20.9.
Figure 20.9. HeapWalker in action.
As you can see, the template for the dialog used for all the Help | About dialog boxes within Windows 3.1-supplied utilities has been located (in both graphical and as-loaded-into-memory formats). If you want to use this template within your own
application, try using code like the code shown in Listing 20.9 to access the undocumented ShellAbout function.
Declare Function ShellAbout Lib "C:\windows\system\shell.dll" (ByVal hWnd As Integer, ByVal Caption As String, ByVal Copyright As String, ByVal hIcon As Integer) As Integer 'All on one line! Sub Form_Load () HInstance% = ShellAbout(hWnd, "My Name", "My Copyright", 0) End Sub
Likewise, if you choose the Resource RCData line within HeapWalker, you see a strange list of abbreviated names—none other than the credit list of Microsoft personnel responsible for Windows 3.1 development!
If you've never seen this list before, you can access the hidden list by choosing Help | About. Then press Ctrl+Shift and double-click about a millimeter to the left of the bottom left corner of the blue square in the Windows Flag icon. The first time,
nothing happens. The second and third time you do it are much more interesting! The third time around gives you the results shown in Figure 20.10.
Figure 20.10. Hidden Microsoft credits scrolling in Win3.1 or Win 3.11.
This time around, it's Bill Gates as the MC. Other times you do it, you will get other illustrious 'Softies like Steve Ballmer or T-Bear....
A curious thing to note about this feature is that the credits text is not visible if you dump SHELL.DLL to text or view it with something like the Norton Utilities. This is because the credits text is encrypted by XORing each character with &H99
(99 hex), so HeapWalker has helped you detect something particularly unusual! If you want to prove this, run the code shown in Listing 20.10.
Sub Form_Load () ShStr$ = Space$(2100) Open "c:\windows\system\shell.dll" For Binary As #1 'UNCOMMENT ONE OF THESE 'Get #1, 38722, ShStr$ 'Win3.1 'Get #1, 38067, ShStr$ 'WfW3.11 Close For n = 1 To 2060 tmp = Asc(Mid$(ShStr$, n, 1)) If tmp >= &H20 Then Debug.Print Chr$(tmp Xor &H99); ElseIf tmp = 0 Then Debug.Print End If Next End Sub
As you can see, HeapWalker is a valuable tool that detects all sorts of objects that might be resident in memory as a result of your application's operation.
This section presents a detailed, behind-the-scenes account of the differences between static and dynamic linking, with an example of how to build one of each.
I will be using the same command-line tools for building both sorts—which demonstrates the similarities between the two methods and also might be useful if you are forced to use a low-speed/low-memory computer that sags under the strain of the full
Visual C++ environment.
The most common examples of static-linked programs are MS-DOS ones, so I'll build a simple application where an MS-DOS BASIC program uses a simple C function to multiply two integers. I'll then implement the same functionality within Visual Basic 4.0
and a DLL, using the same command-line tools for a fair comparison.
Command-line tools, you might be saying, that's the hard way! Don't worry—it's not as hard as it looks at first sight!
Consider the Visual Basic-DOS program SCALCB.BAS shown in Listing 20.11. I've removed the user interface form SCALC.FRM and made it into a minimal, command-line program for the utmost simplicity.
'—————— ' SCALCB.BAS '—————— DECLARE FUNCTION SimpleCalc% CDECL(BYVAL Parm1%, BYVAL Parm2%) DIM Parm1 AS INTEGER DIM Parm2 AS INTEGER INPUT Parm1 INPUT Parm2 PRINT "In BASIC, going into C..." PRINT SimpleCalc (Parm1, Parm2) PRINT "Back into BASIC!"
In summary, two variables are declared and given values by the user. They then are passed to a C function (SimpleCalc), which multiplies them and returns the answer to BASIC for output.
If you are unfamiliar with MS-DOS programming, note that a DOS program always assumes that it is the only application running and therefore does not need to specify a form to which to print. It just takes the input off the screen and throws the answer
back out there afterward. Although Windows can provide multiple DOS sessions, each instance of DOS still behaves in this way within its respective window.
To compile this program, you use the BC.EXE compiler, which has a heritage going back even before Bill Gates' and Paul Allen's work with IBM in the early 1980s (when BC.EXE was known as BASCOM.EXE). This compiler is shown in Listing 20.12.
C:\SMPLCALC>bc scalcb /o /d; Microsoft (R) Visual Basic (TM) for MS-DOS (R) Compiler - Professional Edition Version 1.00 Copyright 1982-1992 Microsoft Corporation. All rights reserved. 42901 Bytes Available 42410 Bytes Free 0 Warning Error(s) 0 Severe Error(s)
This produces an object module—a skeleton of BASIC functionality without the explicit low-level machine code attached yet. A directory listing now includes something like these lines:
SCALCB BAS 298 06/06/95 15:58 SCALCB OBJ 1340 06/06/95 15:59
Table 20.2 shows the full option list for the BASIC compiler.
Option |
Function |
/? |
Displays command-line options |
/A |
Generates assembly listing |
/Ah |
Enables huge dynamic arrays |
/C:n |
Sets default COM buffer size |
/D |
Performs runtime error-checking |
/E |
Enables ON ERROR checking |
/Es |
Enables EMS sharing |
/Fpa |
Enables the alternate math pack |
/G2 |
Generates code for 286 |
/G3 |
Generates code for 386 |
/Ib:n |
Sets number of ISAM buffers; requires ASCII format source file |
/Ie:n |
Reserves non-ISAM EMS |
/Ii:n |
Sets number of ISAM indexes |
/MBF |
Supports Microsoft Binary Format numbers |
/O |
Compiles stand-alone EXE |
/R |
Stores arrays in row-major order |
/S |
Disables string compression |
/T |
Issues no compiler warnings (Terse) |
/V |
Tells ON EVENT to check each statement |
/W |
Tells ON EVENT to check each label |
/X |
Enables RESUME NEXT support |
/Zd |
Provides limited CodeView information |
/Zi |
Provides full CodeView information; requires ASCII format source file |
Next, consider the matching C program, SCALCC.C, shown in Listing 20.13.
/************ * SCALCC.C * ************/ int SimpleCalc(int Parm1, int Parm2); int SimpleCalc(int Parm1, int Parm2) { return (Parm1 * Parm2); }
In summary, two integer variables are received, multiplied, and returned to the calling program. This is compiled in a manner similar to the BASIC program shown in Listing 20.11, except that the C compiler CL.EXE is used instead. CL.EXE is supplied with
Visual C++ and usually is found in the \MSVC\BIN directory. Listing 20.14 shows the compiler output.
C:\SMPLCALC>cl /c /W4 /AL scalcc.c Microsoft (R) C/C++ Optimizing Compiler Version 8.00c Copyright Microsoft Corp 1984-1993. All rights reserved. scalcc.c
This produces another object module—this time, a skeleton of C functionality. A directory listing now includes something like these lines:
SCALCC C 164 06/06/95 15:58 SCALCC OBJ 350 06/06/95 15:59
The next stage is to perform the static link. The BASIC and the C skeletons are joined together; the machine code routines to actually execute the program are copied from the Visual Basic-DOS library VBDCL10E.LIB and attached to the appropriate places
on the combined skeleton. This is performed with the LINK.EXE program, as shown in Listing 20.15.
C:\SMPLCALC>link Microsoft (R) Segmented Executable Linker Version 5.60 Copyright Microsoft Corp 1984-1993. All rights reserved. Object Modules [.obj]: scalcb.obj + Object Modules [.obj]: scalcc.obj Run File [scalcb.exe]: List File [nul.map]: Libraries [.lib]: vbdcl10e.lib Definitions File [nul.def]:
This listing produces a stand-alone executable SCALCB.EXE:
SCALCB EXE 37012 06/06/95 15:59
This executable can be run by typing SCALCB at the DOS command prompt and pressing Enter. See the next section for what it looks like when run.
The process to create a dynamic link library is remarkably similar to that used for static-linked programs, except a few keywords are added to the program, the compiler parameters are slightly altered, and some extra definitions are added to the link
stage in a separate DEF file.
Listing 20.16 shows the altered C program. The Microsoft proprietary _far and _pascal keywords have been added to the declaration in order to ensure that the parameters are not 16-bit and are passed in the correct order. In practice, you should include
the WINDOWS.H header file and use the defined constants FAR and PASCAL, but I've bypassed that here for simplicity.
/************** * SCALCWIN.C * **************/ int _far _pascal SimpleCalc(int Parm1, int Parm2); int _far _pascal SimpleCalc(int Parm1, int Parm2) { return (Parm1 * Parm2); }
This is compiled in a manner similar to the original C program, as shown in Listing 20.17.
C:\SC_WIN>cl /c /W4 /ALw /GD scalcwin.c Microsoft (R) C/C++ Optimizing Compiler Version 8.00c Copyright Microsoft Corp 1984-1993. All rights reserved. scalcwin.c
As before, this listing produces an object module—this time, a skeleton of C DLL functionality. A directory listing now includes something like these lines:
SCALCWIN C 196 06/06/95 16:55 SCALCWIN OBJ 253 06/06/95 16:55
An extra component that must be produced is the DLL link definitions file SCALCWIN.DEF. The contents are shown in Listing 20.18.
LIBRARY SCALCWIN ;Make a DLL... EXETYPE WINDOWS ; ... for Windows CODE PRELOAD MOVEABLE DISCARDABLE ;Build so that Windows can DATA PRELOAD MOVEABLE SINGLE ; relocate it in memory HEAPSIZE 1024 ;Free mem alloc'd to DLL EXPORTS SimpleCalc @1 ;Available function(s) WEP PRIVATE ;Exit Procedure properties
Because you are producing a DLL that is independent of the calling application, you do not need to deal with the Visual Basic calling program at this stage. You merely link the C routine to the appropriate machine code libraries, as shown in Listing
20.19.
C:\SC_WIN>link Microsoft (R) Segmented Executable Linker Version 5.60 Copyright Microsoft Corp 1984-1993. All rights reserved. Object Modules [.obj]: SCALCWIN.OBJ Run File [SCALCWIN.exe]: SCALCWIN.DLL List File [nul.map]: Libraries [.lib]: libw + Libraries [.lib]: ldllcew Definitions File [nul.def]: SCALCWIN.DEF;
This listing produces a dynamic link library as follows:
SCALCWIN DLL 2880 06/06/95 16:56
Figure 20.11 shows the dynamic-linked and static-linked programs running side by side.
Figure 20.11. Examples of dynamic-linked and static-linked programs.
The link between the BASIC code and the C code is made at build time for the static-linked program and at runtime for the dynamic-linked program.
In this chapter, you learned that Dynamic Link Libraries (DLLs) are core-important executable files containing functions that Windows applications can call to perform useful service tasks. You should use DLLs to extend the functionality or ultimate performance of your Visual Basic applications. Check the version number of your DLL when using it. Be aware of the tools in the SDK to analyze your DLLs.